package Modellus.WinUI.ExampleTable;

import Modellus.Core.Animator.IPropertyProxy;
import Modellus.Core.Animator.PropertyProxy;
import Modellus.Core.Controller.IController;
import Modellus.Core.Controller.IControllerListener;
import Modellus.Core.Model.Engine.ICalculationEngine;
import Modellus.Core.Model.Engine.ICalculationEngineIterationChangedListener;
import Modellus.Core.Model.IModel;
import Modellus.Core.Model.IModelDefinition;
import Modellus.Core.Model.IModelDefinitionListener;
import Modellus.Core.Model.IModelListener;
import Modellus.WinUI.Utils.ModellusColorPallete;
import Modellus.WinUI.ExampleTable.ITableListener;
import Modellus.WinUI.ExampleTable.InputTable;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.util.Vector;

public class TableUI extends JScrollPane implements IControllerListener, IModelListener, ICalculationEngineIterationChangedListener, IModelDefinitionListener {
    private Vector<IPropertyProxy> _variables = new Vector<IPropertyProxy>();
    private Vector<Color> _variablesColor = new Vector<Color>();
    private InputTable table = new InputTable();
    private Vector<Vector<Double>> _tableData;
    private IModel _model;
    private ICalculationEngine _calculationEngine;
    private static final int MAX_TABLE_VARIABLES = 10;
    private int _steps = 1;
    private boolean _showBars;
    private Vector<ITableListener> _listeners = new Vector<ITableListener>();
    private IModelDefinition _modelDefinition;

    public TableUI() {
        setOpaque(true);
        initializeGrid();
        initializeParameters();
        buildModelOnTable();
        setTableAsView();
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
    }

    public void setModel(IModel model) {
        _model = model;
        _model.getController().addListener(this);
        _model.addListener(this);
        _modelDefinition = _model.getModelDefinition();
        _modelDefinition.addListener(this);
        _calculationEngine = _model.getCalculationEngine();
        _calculationEngine.addIterationChangedListener(this);
        IController controller = _model.getController();
        controller.addListener(this);
        for (int variableIndex = 0; variableIndex < MAX_TABLE_VARIABLES; variableIndex++) {
            _variables.add(new PropertyProxy(_model));
            _variablesColor.add(ModellusColorPallete.BLUE);
        }
        initializeParameters();
        createData();
    }

    public JTable getTable() {
        return table;
    }

    @Override
    public Dimension getMinimumSize() {
        return new Dimension(50, 50);
    }

    protected void paintComponent(Graphics g) {
        update(g);
        super.paintComponent(g);
    }

    public void update(Graphics g) {
        JViewport viewport = this.getColumnHeader();
        if (viewport != null) {
            viewport.setOpaque(false);
            viewport.getView().setBackground(ModellusColorPallete.BLUE);
            viewport.setBackground(ModellusColorPallete.BLUE);
        }
        viewport = this.getViewport();
        if (viewport != null) {
            viewport.setOpaque(false);
            viewport.getView().setBackground(ModellusColorPallete.BLUE);
            viewport.setBackground(ModellusColorPallete.BLUE);
        }
    }

    private void buildModelOnTable() {
        TableColumnModel columnModel = table.getColumnModel();
        Vector<String> headers = new Vector<String>();
        for (IPropertyProxy variable : _variables)
            if (variable.isTerm())
                headers.add(variable.getName());
        DefaultTableModel model = new DefaultTableModel(_tableData, headers);
        table.setModel(model);
        int columnIndex = 0;
        for (int variableIndex = 0; variableIndex < _variables.size(); variableIndex++) {
            IPropertyProxy variable = _variables.get(variableIndex);
            if (variable.isTerm()) {
                TableColumn column = columnModel.getColumn(columnIndex);
                column.setCellRenderer(new BarCellRenderer(_model));
                column.setHeaderRenderer(new PropertyCellRenderer(_model, variable));
                columnIndex++;
            }
        }
    }

    private void createDataFromModel() {
        ICalculationEngine calculationEngine = _model.getController().getCalculationEngine();
        int iterationCount = calculationEngine.getIterationsCalculatedCount();
        for (int iteration = 0; iteration < iterationCount; iteration++)
            if (iteration % _steps == 0) {
                Vector<Double> currentIteration = createTableRow(calculationEngine, iteration);
                _tableData.add(currentIteration);
            }
    }

    private void initializeGrid() {
        setBorder(new EmptyBorder(getInsets()));
        setOpaque(false);
        getViewport().setOpaque(false);
    }

    private void initializeParameters() {
        _tableData = new Vector<Vector<Double>>();
    }

    private void setTableAsView() {
        setViewportView(table);
    }

    public void controllerReset() {
        initializeParameters();
        createData();
        reset();
    }

    public void controllerEndReached() {
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
    }

    public void controllerMinIndependentValueChanged() {
    }

    public void controllerMaxIndependentValueChanged() {
    }

    public void controllerStepChanged() {
    }

    public void controllerPlayed() {
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
    }

    public void controllerOneStepPlayed() {
    }

    public void controllerPaused() {
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
    }

    public void controllerReplayed() {
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
    }

    public void controllerHalted() {
        setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
    }

    public void controllerBeforeOneStepPlayed() {
    }

    public void controllerStarted() {
    }

    public void controllerAutoPlayChanged() {
    }

    private void createData() {
        createDataFromModel();
        buildModelOnTable();
        setTableAsView();
    }

    private void updateData() {
        addIteration();
        scrollToIteration();
    }

    private void scrollToIteration() {
        int iteration = _calculationEngine.getIteration();
        Rectangle rowRectangle = table.getCellRect(iteration, 0, true);
        table.scrollRectToVisible(rowRectangle);
    }

    private void addIteration() {
        int iteration = _calculationEngine.getIteration();
        int iterationCount = _calculationEngine.getIterationsCalculatedCount();
        if (iteration == iterationCount - 1 && iteration % _steps == 0) {
            removeTableRow(iteration);
            Vector<Double> currentIteration = createTableRow(_calculationEngine, iterationCount - 1);
            ((DefaultTableModel) table.getModel()).addRow(currentIteration);
        }
    }

    private Vector<Double> createTableRow(ICalculationEngine calculationEngine, int iteration) {
        Vector<Double> currentIteration = new Vector<Double>();
        for (IPropertyProxy variable : _variables)
            if (variable.isTerm())
                currentIteration.add(calculationEngine.get(variable.getSituation(), variable.getName(), iteration));
        return currentIteration;
    }

    private void removeTableRow(int iteration) {
        DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
        if (tableModel.getRowCount() > iteration)
            tableModel.removeRow(iteration);
    }

    public void modelChanged() {
        initializeParameters();
        createData();
    }

    public void modelCleared() {
    }

    public void decimalPlacesChanged() {
    }

    public void exponentialThresholdChanged() {
    }

    public void situationActiveChanged() {
    }

    public void isProtectedChanged() {
    }

    public void useDegreesChanged() {
    }

    public void calculationEngineIterationChanged() {
        updateData();
    }

    public IPropertyProxy getVariable(int variableIndex) {
        return _variables.get(variableIndex);
    }

    public void setVariable(int variableIndex) {
        reset(variableIndex);
    }

    public int getSteps() {
        return _steps;
    }

    public void setSteps(int steps) {
        _steps = steps;
        initializeParameters();
        createData();
        onStepsChanged();
    }

    public int getVariableSituation(int variableIndex) {
        return _variables.get(variableIndex).getSituation();
    }

    public void setVariableSituation(int variableIndex, int situation) {
        IPropertyProxy property = _variables.get(variableIndex);
        if (property.getSituation() == situation)
            return;
        property.setSituation(situation);
        reset(variableIndex);
    }

    public Color getVariableColor(int variableIndex) {
        return _variablesColor.get(variableIndex);
    }

    public void setVariableColor(int variableIndex, Color color) {
        _variablesColor.set(variableIndex, color);
        reset();
    }

    void reset() {
        TableColumnModel columnModel = table.getColumnModel();
        int columnIndex = 0;
        for (int variableIndex = 0; variableIndex < _variables.size(); variableIndex++) {
            IPropertyProxy variable = _variables.get(variableIndex);
            if (variable.isTerm()) {
                TableColumn column = columnModel.getColumn(columnIndex);
                BarCellRenderer renderer = (BarCellRenderer) column.getCellRenderer();
                renderer.setShowBars(_showBars);
                renderer.setColor(_variablesColor.get(variableIndex));
                columnIndex++;
            }
        }
        repaint();
    }

    void reset(int variableIndex) {
        initializeParameters();
        buildModelOnTable();
        IPropertyProxy variable = _variables.get(variableIndex);
        if (variable.isTerm()) {
            TableColumnModel columnModel = table.getColumnModel();
            TableColumn column = columnModel.getColumn(0);
            if (column.getCellRenderer() instanceof BarCellRenderer) {
                BarCellRenderer renderer = (BarCellRenderer) column.getCellRenderer();
                renderer.setShowBars(_showBars);
                renderer.setColor(_variablesColor.get(variableIndex));
            }
        }
        createDataFromModel();
        onVariablesChanged();
    }

    public boolean getShowBars() {
        return _showBars;
    }

    public void setShowBars(boolean showBars) {
        _showBars = showBars;
        reset();
        onShowBarsChanged();
    }

    public Vector<IPropertyProxy> getVariables() {
        return _variables;
    }

    public void addListener(ITableListener listener) {
        _listeners.add(listener);
    }

    public void onShowBarsChanged() {
        for (ITableListener listener : _listeners)
            listener.showBarsChanged();
    }

    public void onVariablesChanged() {
        for (ITableListener listener : _listeners)
            listener.variablesChanged();
    }

    public void onStepsChanged() {
        for (ITableListener listener : _listeners)
            listener.stepsChanged();
    }

    public void clear() {
        setShowBars(false);
        setSteps(1);
        for (int variableIndex = 0; variableIndex < MAX_TABLE_VARIABLES; variableIndex++) {
            _variables.get(variableIndex).invalidate();
            _variables.get(variableIndex).setSituation(0);
            _variablesColor.set(variableIndex, ModellusColorPallete.BLUE);
        }
        initializeParameters();
        createData();
        onVariablesChanged();
    }

    /**
     * Fired when the entire definition of the model changes.
     */
    public void definitionChanged() {
        Vector<String> terms = _modelDefinition.getTerms();
        if (!_variables.get(0).isTerm())
            _variables.get(0).setName(_modelDefinition.getIndependentName());
        if (terms.size() > 1 && !_variables.get(1).isTerm())
            _variables.get(1).setName(terms.get(0));
        initializeParameters();
        createData();
        setShowBars(_showBars);
    }

    /**
     * Fired when a different term type is set.
     */
    public void termTypeChanged() {
    }
}


